/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.peer.controller.ds;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.ourgrid.common.interfaces.DiscoveryServiceClient;
import org.ourgrid.common.interfaces.RemoteWorkerProvider;
import org.ourgrid.common.statistics.beans.peer.Peer;
import org.ourgrid.common.statistics.control.PeerControl;
import org.ourgrid.discoveryservice.DiscoveryServiceConstants;
import org.ourgrid.peer.PeerConfiguration;
import org.ourgrid.peer.PeerConstants;
import org.ourgrid.peer.controller.messages.DiscoveryServiceClientMessages;
import org.ourgrid.peer.dao.DiscoveryServiceClientDAO;
import org.ourgrid.peer.dao.PeerCertificationDAO;

import sun.security.provider.certpath.X509CertPath;
import br.edu.ufcg.lsd.commune.api.FailureNotification;
import br.edu.ufcg.lsd.commune.api.InvokeOnDeploy;
import br.edu.ufcg.lsd.commune.api.RecoveryNotification;
import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.identification.ServiceID;
import br.edu.ufcg.lsd.commune.network.certification.CertificateCRLPair;
import br.edu.ufcg.lsd.commune.network.certification.CertificationUtils;

/**
 *
 */
public class DiscoveryServiceClientController implements DiscoveryServiceClient {
	
	private ServiceManager serviceManager;

	@InvokeOnDeploy
	public void init(ServiceManager serviceManager) {
		this.serviceManager = serviceManager;
	}
	
	public void hereIsRemoteWorkerProviderList(List<String> workerProviders) {
		
//		System.out.println( "DiscoveryServiceClientController.hereIsRemoteWorkerProviderList()");
//		for ( String workerProvider : workerProviders ) {
//			System.out.println("\t" + workerProvider);
//		}
		DiscoveryServiceClientDAO dao = serviceManager.getDAO(DiscoveryServiceClientDAO.class);
		
		Set<String> oldProvidersAddress = new HashSet<String>();

		Set<DeploymentID> set = dao.getProviders().keySet();
		for ( DeploymentID deploymentID : set ) {
			oldProvidersAddress.add( deploymentID.getContainerID().getUserAtServer() );
		}
		
		for (String peerAddress : workerProviders) {
			
			if (peerAddress.equals(serviceManager.getMyDeploymentID().getContainerID().getUserAtServer())) {
				continue;
			}
			
			if (!oldProvidersAddress.remove( peerAddress )){
				
				ServiceID providerID = createProviderServiceID(peerAddress);
				
				serviceManager.registerInterest(PeerConstants.DS_CLIENT, providerID.toString(), 
						RemoteWorkerProvider.class);
				serviceManager.getLog().debug("Receiving worker provider with id [" + peerAddress + "]");
			}
			
		}
		
		for (String address : oldProvidersAddress) {
			ServiceID providerServiceID = createProviderServiceID(address);
			markProviderAsDown(dao.getProviderDeploymentID(providerServiceID));
		}
	}

	private ServiceID createProviderServiceID(String address) {
		String[ ] splitAddress = address.split("@");
		return new ServiceID(splitAddress[0], splitAddress[1], 
				PeerConstants.MODULE_NAME, PeerConstants.REMOTE_ACCESS_OBJECT_NAME);
		
	}

	private void markProviderAsDown(DeploymentID deploymentID) {
		
		DiscoveryServiceClientDAO dao = serviceManager.getDAO(DiscoveryServiceClientDAO.class);
		
		RemoteWorkerProvider provider = dao.removeRemoteWorkersProvider(deploymentID);
		
		if (provider != null) {
			
			serviceManager.release(provider);
			Peer peer = PeerControl.getInstance().getPeerByCommuneAddress(deploymentID, serviceManager);
			
			if (peer != null) {
				PeerControl.getInstance().peerIsDown(serviceManager, peer.getDNdata());
			}
		}
		
	}

	@RecoveryNotification
	public void workerProviderIsUp(RemoteWorkerProvider rwp, DeploymentID deploymentID, 
			X509CertPath providerCertPath) {
//		System.out.println( "DiscoveryServiceClientController.workerProviderIsUp() " + deploymentID.getContainerID().getUserAtServer());
		if (CertificationUtils.getCertSubjectDN(serviceManager.getMyCertPath()).equals(
				CertificationUtils.getCertSubjectDN(providerCertPath))) {
			
			serviceManager.getLog().debug(
					DiscoveryServiceClientMessages.getSameCertificateDNMessage(deploymentID));
			serviceManager.release(rwp);
			return;
		}
		
		Collection<CertificateCRLPair> receivedPeersCAsData = serviceManager.getDAO(
				PeerCertificationDAO.class).getReceivedPeersCAsData();
		
		if (receivedPeersCAsData != null && !receivedPeersCAsData.isEmpty()) {
			
			if(!CertificationUtils.isCertificateValid(providerCertPath)) {
				serviceManager.getLog().warn(DiscoveryServiceClientMessages.getInvalidCertPathMessage(deploymentID));
				serviceManager.release(rwp);
				return;
			}
			
			if(!CertificationUtils.isCertPathIssuedByCA(providerCertPath, receivedPeersCAsData)) {
				serviceManager.getLog().warn(DiscoveryServiceClientMessages.getNonIssuedCertPathMessage(deploymentID));
				serviceManager.release(rwp);
				return;
			}
			
		}
		
		DiscoveryServiceClientDAO dao = serviceManager.getDAO(DiscoveryServiceClientDAO.class);
		dao.addRemoteWorkersProvider(deploymentID, rwp);
		
		PeerControl.getInstance().insertPeer(deploymentID.getServiceID(),serviceManager, 
				CertificationUtils.getCertSubjectDN(providerCertPath));
	}

	@FailureNotification
	public void workerProviderIsDown(RemoteWorkerProvider rwp, DeploymentID deploymentID,
				X509CertPath providerCertPath) {
		markProviderAsDown(deploymentID);
	}

	/* (non-Javadoc)
	 * @see org.ourgrid.common.interfaces.DiscoveryServiceClient#hereAreDiscoveryServices(java.util.List)
	 */
	public void hereAreDiscoveryServices(List<String> discoveryServices) {
		DiscoveryServiceClientDAO dao = serviceManager.getDAO(DiscoveryServiceClientDAO.class);
		boolean modified = false; 
		for (String dsAdress : discoveryServices) {
			String[] splitAddress = dsAdress.split("@");
			ServiceID dsID = new ServiceID(splitAddress[0], splitAddress[1], 
					DiscoveryServiceConstants.MODULE_NAME, DiscoveryServiceConstants.DS_OBJECT_NAME);
			modified |= dao.addDsID(dsID);
		}
		if (modified) {
			List<String> dsAdresses = new ArrayList<String>();
			for (ServiceID dsID : dao.getDsIDs()) {
				dsAdresses.add(dsID.getContainerID().getUserAtServer());
			}
			try {
				PeerConfiguration.persistNetwork(dsAdresses, serviceManager.getContainerContext());
			} catch (IOException e) {
				serviceManager.getLog().error(
						"Discovery Service Network data could not be persisted.", e);
			}
		}
	}

}
